// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;

namespace SnapObjects.Data
{
    internal abstract class SqlWhereAndOr<TCondition> : SqlBuilderComb, ISqlWhereAndOr<TCondition>
    {
        protected SqlBuildClause _clause;
        public SqlWhereAndOr(ISqlBuilder builder, SqlBuildClause where) : base(builder)
        {
            _clause = where;
        }

        protected abstract TCondition BuilderResult();

        protected TCondition AddCondition(SqlElement condition, SqlStitchingType stitchingType)
        {
            if (_clause.TryGet(out WhereElement element))
            {
                _clause.Changed();

                element.Condition.AndOr.Add(condition, stitchingType);
            }
            else
            {
                throw new InvalidOperationException("Please call 'Where' before adding conditions.");
            }

            return this.BuilderResult();
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL expression 
        ///     on the left of the operator and a SQL parameter on the right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="parameter">
        ///     An ISqlParameter object which represents a SQL parameter on the right of the operator.  
        ///     It can be created by calling the SqlBuilder.Parameter method.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, ISqlParameter parameter)
        {
            _clause.AddParameter(parameter);

            return this.AddCondition(
                new ConditionElement(left, SqlOperator.Equals, parameter, SqlRightType.Parameter),
                SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL expression 
        ///     to the left of the operator and a SQL parameter to the right of the operator.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="parameter">
        ///     An ISqlParameter object which represents a SQL parameter on the right of the operator. 
        ///     It can be created by calling the SqlBuilder.Parameter method.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, SqlBinaryOperator sqlOperator, ISqlParameter parameter)
        {
            _clause.AddParameter(parameter);

            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), parameter, SqlRightType.Parameter),
                SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies SQL expressions
        ///     on both the left and right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="right">A SQL expression on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, string right)
        {
            return this.AndWhere(left, SqlBinaryOperator.Equals, right);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies SQL expressions
        ///     on both the left and right of the operator.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="right">A SQL expression on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, SqlBinaryOperator sqlOperator, string right)
        {
            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), right, SqlRightType.Clause),
                SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL
        ///     expression on the left of the operator and a SQL subquery on the right. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, ISqlBuilder sqlBuilder)
        {
            return this.AndWhere(left, SqlBinaryOperator.Equals, sqlBuilder);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL expression 
        ///     on the left of the operator and a SQL subquery on the right.
        /// </summary>
        /// <param name="left">
        ///     A SQL expression on the left of the operator.
        /// </param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(string left, SqlBinaryOperator sqlOperator, ISqlBuilder sqlBuilder)
        {
            return this.AndWhere(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), sqlBuilder);
        }

        private TCondition AndWhere(string left, SqlOperator sqlOperator, ISqlBuilder sqlBuilder)
        {
            var builder = (SqlBuilderBase)sqlBuilder;

            _clause.AddParameters(builder.Statement.Parameters);

            return this.AddCondition(
                new ConditionElement(left, sqlOperator, builder.Statement, SqlRightType.Statement),
                SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition (specified by an ISqlWhereCondition object) to the WHERE clause.
        /// </summary>
        /// <param name="condition">
        ///     An ISqlWhereCondition which represents a search condition in the WHERE Clause. 
        ///     This object can be created by using the SqlBuilder.Where method and so on.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhere(ISqlWhereCondition condition)
        {
            var hc = (SqlWhereCondition)condition;

            if (hc.Clause.TryGet(out WhereElement cwhere))
            {
                if (_clause.TryGet(out WhereElement element))
                {
                    _clause.Changed();

                    element.Condition.AndOr.Add(cwhere.Condition, SqlStitchingType.And);
                }
                else
                {
                    _clause.Add(cwhere);
                }

                _clause.AddParameters(hc.BuilderParameters);
            }

            return this.BuilderResult();
        }

        /// <summary>
        ///     Adds the AND logical operator and a raw SQL to the WHERE clause.
        /// </summary>
        /// <param name="clause">A string of raw SQL to be added to the WHERE clause.</param>
        /// <param name="parameters">
        ///     (Optional) An array of ISqlParameter objects which define the SQL parameters used in the raw SQL.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereRaw(string clause, params ISqlParameter[] parameters)
        {
            var where = new ConditionElement(
                null, SqlOperator.Equals, clause, SqlRightType.Clause);

            _clause.AddParameters(parameters);

            return this.AddCondition(where, SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL expression on the left of the operator
        ///     and a specific value on the right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">An expression on the left of the operator.</param>
        /// <param name="value">A specific value on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereValue(string left, object value)
        {
            return this.AndWhereValue(left, SqlBinaryOperator.Equals, value);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and specifies a SQL expression on the 
        ///     left of the operator and a specific value on the right of the operator.
        /// </summary>
        /// <param name="left">An expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the expression on the left and the value on the right.
        /// </param>
        /// <param name="value">
        ///     The specific value on the right of the operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereValue(string left, SqlBinaryOperator sqlOperator, object value)
        {
            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), value, SqlRightType.Value),
                SqlStitchingType.And);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and uses IS NULL
        ///     operator to search for values that are null.
        /// </summary>
        /// <param name="left">
        ///     A SQL expression on the left of the IS NULL operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereIsNull(string left)
        {
            return this.AndWhere(left, SqlBinaryOperator.Is, "null");
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and uses IS NOT NULL 
        ///     operator to search for values that are not null.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the IS NOT NULL operator.</param>
        /// <returns>
        ///     Returns the ISqlWhereAndOr object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereIsNotNull(string left)
        {
            return this.AndWhere(left, SqlBinaryOperator.IsNot, "null");
        }

        /// <summary>
        ///     Adds the AND logic operator and a search condition to the WHERE clause; and uses EXISTS to test for 
        ///     the existence of the record returned from a subquery.
        /// </summary>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the EXISTS operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereExists(ISqlBuilder sqlBuilder)
        {
            return this.AndWhere("", SqlOperator.Exists, sqlBuilder);
        }

        /// <summary>
        ///     Adds the AND logical operator and a search condition to the WHERE clause; and uses the NOT EXISTS operator
        ///     to test if the record returned from a subquery does not exist.
        /// </summary>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the NOT EXISTS operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition AndWhereNotExists(ISqlBuilder sqlBuilder)
        {
            return this.AndWhere("", SqlOperator.NotExists, sqlBuilder);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression to the 
        ///     left of the operator and a SQL parameter to the right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="parameter">
        ///     An ISqlParameter object which represents a SQL parameter. 
        ///     It can be created by calling the SqlBuilder.Parameter method.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, ISqlParameter parameter)
        {
            return this.OrWhere(left, SqlBinaryOperator.Equals, parameter);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression
        ///     on the left of the operator and a SQL parameter on the right of the operator.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="parameter">
        ///     An ISqlParameter object which represents a SQL parameter. 
        ///     It can be created by calling the SqlBuilder.Parameter method.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, SqlBinaryOperator sqlOperator, ISqlParameter parameter)
        {
            _clause.AddParameter(parameter);

            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), parameter, SqlRightType.Parameter),
                SqlStitchingType.Or);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies SQL expressions
        ///     on both the left and right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="right">A SQL expression on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, string right)
        {
            return this.OrWhere(left, SqlBinaryOperator.Equals, right);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies SQL expressions 
        ///     on both the left and right of the operator.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="right">A SQL expression on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, SqlBinaryOperator sqlOperator, string right)
        {
            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), right, SqlRightType.Clause),
                SqlStitchingType.Or);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression on 
        ///     the left of the operator and a SQL subquery on the right. The operator is '='.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, ISqlBuilder sqlBuilder)
        {
            return this.OrWhere(left, SqlBinaryOperator.Equals, sqlBuilder);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression 
        ///     on the left of the operator and a SQL subquery on the right.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the two expressions on the left and right.
        /// </param>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(string left, SqlBinaryOperator sqlOperator, ISqlBuilder sqlBuilder)
        {
            return this.OrWhere(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), sqlBuilder);
        }

        private TCondition OrWhere(string left, SqlOperator sqlOperator, ISqlBuilder sqlBuilder)
        {
            var builder = (SqlBuilderBase)sqlBuilder;

            _clause.AddParameters(builder.Statement.Parameters);

            return this.AddCondition(
                new ConditionElement(left, sqlOperator, builder.Statement, SqlRightType.Statement),
                SqlStitchingType.Or);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition (specified by an ISqlWhereCondition object) to the WHERE clause.
        /// </summary>
        /// <param name="condition">
        ///     An ISqlWhereCondition which represents a search condition for WHERE clause. 
        ///     This object can be created by using the SqlBuilder.Where method and so on.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhere(ISqlWhereCondition condition)
        {
            var hc = (SqlWhereCondition)condition;

            if (hc.Clause.TryGet(out WhereElement cwhere))
            {
                if (_clause.TryGet(out WhereElement element))
                {
                    _clause.Changed();

                    element.Condition.AndOr.Add(cwhere.Condition, SqlStitchingType.Or);
                }
                else
                {
                    _clause.Add(cwhere);
                }

                _clause.AddParameters(hc.BuilderParameters);
            }

            return this.BuilderResult();
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; 
        ///     and uses IS NULL operator to search for values that are null.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the IS NULL operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereIsNull(string left)
        {
            return this.OrWhere(left, SqlBinaryOperator.Is, "null");
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and uses the 
        ///     IS NOT NULL operator to search for values that are not null.
        /// </summary>
        /// <param name="left">A SQL expression on the left of the IS NOT NULL operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereIsNotNull(string left)
        {
            return this.OrWhere(left, SqlBinaryOperator.IsNot, "null");
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and uses EXISTS to test for
        ///     the existence of the record returned from a subquery.
        /// </summary>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the EXISTS operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereExists(ISqlBuilder sqlBuilder)
        {
            return this.OrWhere("", SqlOperator.Exists, sqlBuilder);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and uses the NOT EXISTS 
        ///     operator to test if the record returned from a subquery does not exist.
        /// </summary>
        /// <param name="sqlBuilder">
        ///     An ISqlBuilder object which represents a SQL subquery on the right of the NOT EXISTS operator.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereNotExists(ISqlBuilder sqlBuilder)
        {
            return this.OrWhere("", SqlOperator.NotExists, sqlBuilder);
        }

        /// <summary>
        ///     Adds the OR logical operator and a raw SQL to the WHERE clause.
        /// </summary>
        /// <param name="clause">
        ///     A string of raw SQL to be added to the WHERE clause.
        /// </param>
        /// <param name="parameters">
        ///     (Optional) An array of ISqlParameter objects which define the SQL parameters used in the raw SQL.
        /// </param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereRaw(string clause, params ISqlParameter[] parameters)
        {
            _clause.AddParameters(parameters);

            return this.AddCondition(
                new ConditionElement(null, SqlOperator.Equals, clause, SqlRightType.Clause),
                SqlStitchingType.Or);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression on the 
        ///     left the operator and a specific value on the right of the operator. The operator is '='.
        /// </summary>
        /// <param name="left">An expression on the left of the operator.</param>
        /// <param name="value">The specific value on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereValue(string left, object value)
        {
            return this.AddCondition(
                new ConditionElement(left, SqlOperator.Equals, value, SqlRightType.Value),
                SqlStitchingType.Or);
        }

        /// <summary>
        ///     Adds the OR logical operator and a search condition to the WHERE clause; and specifies a SQL expression 
        ///     on the left the operator and a specific value on the right of the operator.
        /// </summary>
        /// <param name="left">An expression on the left of the operator.</param>
        /// <param name="sqlOperator">
        ///     An enumeration value of SqlBinaryOperator, which is the operator to test the expression on the left and the value on the right.
        /// </param>
        /// <param name="value">The specific value on the right of the operator.</param>
        /// <returns>
        ///     Returns the TBuilder object which can be used to add more search conditions to the current WHERE clause.
        /// </returns>
        public TCondition OrWhereValue(string left, SqlBinaryOperator sqlOperator, object value)
        {
            return this.AddCondition(
                new ConditionElement(left, SqlOperatorConverter.ToSqlOperator(sqlOperator), value, SqlRightType.Value),
                SqlStitchingType.Or);
        }
    }
}
